package com.Fow.core.rest.client;

import com.Fow.core.platform.exception.*;
import com.Fow.core.platform.json.JSONBinder;
import com.Fow.core.platform.web.rest.exception.APIErrorCode;
import com.Fow.core.platform.web.rest.exception.ErrorResponse;
import com.Fow.core.platform.web.rest.exception.FieldError;
import com.Fow.core.platform.web.rest.exception.InvalidRequestException;
import com.Fow.core.util.StopWatch;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.*;
import org.apache.http.entity.AbstractHttpEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.ByteArrayBody;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.util.List;

/**
 * Created by greg.chen on 2014/10/21.
 */
public class RESTServiceClient {
    private static Logger logger = LoggerFactory.getLogger(RESTServiceClient.class);
    private static final String HEADER_CLIENT_ID = "Client-Id";
    private static final String HEADER_SECRET_KEY = "Secret-Key";
    private static final String HEADER_SESSION_ID= "Session-Id";
    private static final String HEADER_VISITOR_ID= "Visitor-Id";
    private static final String CHARSET_UTF_8 = "UTF-8";

    private String clientId;
    private String secretKey;
    private String visitorId;
    private String sessionId;
    private String domain;

    public String getDomain() {
        return domain;
    }

    public void setDomain(String domain) {
        this.domain = domain;
    }

    public String getClientId() {
        return clientId;
    }

    public void setClientId(String clientId) {
        this.clientId = clientId;
    }

    public String getSecretKey() {
        return secretKey;
    }

    public String getSessionId(){return sessionId;}

    public void setSessionId(String sessionId){this.sessionId=sessionId;}

    public String getVisitorId(){return visitorId;}

    public void setVisitorId(String visitorId){this.visitorId=visitorId;}

    public void setSecretKey(String secretKey) {
        this.secretKey = secretKey;
    }

    public RESTServiceClient(){}

    public RESTServiceClient(String clientId, String secretKey){
        this.clientId = clientId;
        this.secretKey = secretKey;
    }

    private int getStatusCode(HttpResponse response){
        return  response.getStatusLine().getStatusCode();
    }

    private String getResponseText(HttpResponse response) throws Exception {
        byte[] content = EntityUtils.toByteArray(response.getEntity());
        String responseText = new String(content, CHARSET_UTF_8);

        return responseText;
    }


    private void setSessionId(HttpResponse response) {
        Header header = response.getFirstHeader("Set-Cookie");
        if(header != null){
            HeaderElement[] elements = header.getElements();
            for (HeaderElement element : elements){
                if("JSESSIONID".equalsIgnoreCase(element.getName())){
                    this.sessionId = element.getValue();
                }
            }
        }
    }

    public <T> T get(String uri, Class<T> responseClass) throws Exception {
        StopWatch stopWatch = new StopWatch();
        logger.debug("send request, url={}, method={}", uri, "Get");
        logger.debug("====== http request begin ======");
        HttpRequestBase request = new HttpGet(this.domain + uri);
        prepareHeaders(request);
        logger.debug("JESSIONID=" + getSessionId());
        request.setHeader("Cookie","JSESSIONID=" + getSessionId());

        HttpResponse response =createDefaultHttpClient().execute(request);

        setSessionId(response);

        logger.debug("====== http request end ======");
        logger.debug("received response, statusCode={}, elapsed={}",getStatusCode(response), stopWatch.elapsedTime());
        int responseCode = getStatusCode(response);
        String responseText = getResponseText(response);
        request.releaseConnection();
        validateStatusCode(responseCode, responseText);
        return JSONBinder.binder(responseClass).fromJSON(responseText);
    }

    public <T, U> T post(String uri, Class<T> responseClass, U requestObj) throws Exception {
        StopWatch stopWatch = new StopWatch();
        logger.debug("send request, url={}, method={}", this.domain + uri, "Post");
        logger.debug("====== http request begin ======");
        HttpPost request = new HttpPost(this.domain + uri);
        prepareHeaders(request);
        logger.debug("JSESSIONID=" + getSessionId());
        request.setHeader("Cookie","JSESSIONID=" + getSessionId());

        if(requestObj != null){
            String body = JSONBinder.binder((Class<U>) requestObj.getClass()).toJSON(requestObj);
            AbstractHttpEntity entity = new StringEntity(body, CHARSET_UTF_8);
            request.setEntity(entity);
        }

        HttpResponse response =createDefaultHttpClient().execute(request);

        setSessionId(response);

        logger.debug("====== http request end ======");
        logger.debug("received response, statusCode={}, elapsed={}", getStatusCode(response), stopWatch.elapsedTime());

        int responseCode = getStatusCode(response);
        String responseText = getResponseText(response);
        request.releaseConnection();
        validateStatusCode(responseCode, responseText);
        return JSONBinder.binder(responseClass).fromJSON(responseText);
    }

    @SuppressWarnings("unchecked")
    public <T, U> T put(String uri, Class<T> responseClass, U requestObj) throws Exception {
        StopWatch stopWatch = new StopWatch();
        logger.debug("send request, url={}, method={}", this.domain + uri, "Put");
        logger.debug("====== http request begin ======");
        HttpPut request = new HttpPut(this.domain + uri);
        prepareHeaders(request);
        logger.debug("JSESSIONID=" + getSessionId());
        request.setHeader("Cookie","JSESSIONID=" + getSessionId());

        if(requestObj != null){
            String body = JSONBinder.binder((Class<U>) requestObj.getClass()).toJSON(requestObj);
            AbstractHttpEntity entity = new StringEntity(body, CHARSET_UTF_8);
            request.setEntity(entity);
        }

        HttpResponse response =createDefaultHttpClient().execute(request);
        logger.debug("====== http request end ======");
        logger.debug("received response, statusCode={}, elapsed={}", getStatusCode(response), stopWatch.elapsedTime());

        setSessionId(response);

        int responseCode = getStatusCode(response);
        String responseText = getResponseText(response);
        request.releaseConnection();
        validateStatusCode(responseCode, responseText);

        return JSONBinder.binder(responseClass).fromJSON(responseText);
    }

    @SuppressWarnings("unchecked")
    public <T, U> T delete(String uri, Class<T> responseClass) throws Exception {
        StopWatch stopWatch = new StopWatch();
        logger.debug("send request, url={}, method={}", this.domain + uri, "Delete");
        logger.debug("====== http request begin ======");

        HttpRequestBase request = new HttpDelete(this.domain + uri);
        prepareHeaders(request);
        logger.debug("JSESSIONID=" + getSessionId());
        request.setHeader("Cookie","JSESSIONID=" + getSessionId());

        HttpResponse response =createDefaultHttpClient().execute(request);

        setSessionId(response);

        logger.debug("====== http request end ======");
        logger.debug("received response, statusCode={}, elapsed={}", response.getStatusLine().getStatusCode(), stopWatch.elapsedTime());

        int responseCode = getStatusCode(response);
        String responseText = getResponseText(response);
        request.releaseConnection();
        validateStatusCode(responseCode, responseText);

        return JSONBinder.binder(responseClass).fromJSON(responseText);
    }

    /**
     * 写入请求头
     * @param request
     */
    private void prepareHeaders(HttpRequestBase request) {
        request.setHeader(HEADER_CLIENT_ID,this.clientId);
        request.setHeader(HEADER_SECRET_KEY,this.secretKey);
        request.setHeader("Content-Type", HTTPConstants.CONTENT_TYPE_JSON);
        request.setHeader(HEADER_VISITOR_ID,this.visitorId);
    }

    private void prepareHeadersForFile(HttpRequestBase request){
        request.setHeader(HEADER_CLIENT_ID,this.clientId);
        request.setHeader(HEADER_SECRET_KEY,this.secretKey);
        request.setHeader(HEADER_VISITOR_ID,this.visitorId);
    }

    /**
     * 验证Response code
     * @param responseCode
     * @param responseText
     */
    private void validateStatusCode(int responseCode, String responseText) {
        logger.debug("response status code => {}",responseCode);
        if (responseCode >= HTTPConstants.SC_OK && responseCode <= HTTPConstants.SC_MULTI_STATUS) {
            return;
        }
        ErrorResponse errorResponse = JSONBinder.binder(ErrorResponse.class).fromJSON(responseText);

        if (responseCode == HTTPConstants.SC_BAD_REQUEST) {

            if(APIErrorCode.LOGIN_REQUIRED.getCode().equals(errorResponse.getErrorCode())){
                throw new LoginRequiredException(errorResponse.getMessage());
            } else if(APIErrorCode.SESSION_EXPIRED.getCode().equals(errorResponse.getErrorCode())){
                throw new SessionExpiredException(errorResponse.getMessage());
            } else if (APIErrorCode.VisitorNotFound.getCode().equals(errorResponse.getErrorCode())){
                throw new VisitorNotFoundException(errorResponse.getMessage());
            }
            else {
                List<FieldError> fieldErrorList = errorResponse.getFieldErrors();
                if(fieldErrorList != null && fieldErrorList.size() > 0)
                    throw new InvalidRequestException(errorResponse.getFieldErrors().get(0).getField(), errorResponse.getFieldErrors().get(0).getMessage());
                else
                    throw new InvalidRequestException(errorResponse.getMessage());
            }
        }
        else if(responseCode == HTTPConstants.SC_NOT_FOUND){
            throw new ResourceNotFoundException(errorResponse.getMessage());
        }
        else if(responseCode == HTTPConstants.SC_UNAUTHORIZED){
            throw new UserAuthorizationException(errorResponse.getMessage());
        }
        else if(responseCode == HTTPConstants.SC_FORBIDDEN){
            throw new PermissionRequiredException(errorResponse.getMessage());
        }

        logger.error(responseText);
        throw new HTTPException("invalid response status code, statusCode=" + responseCode + "error message:" + responseText);
    }

    /**
     * 获取HttpClient
     * @return
     */
    private HttpClient createDefaultHttpClient() {
        HttpClient client =  new DefaultHttpClient();

        return client;
    }

    public <T, U> T postPhoto(String uri, Class<T> responseClass, File file) throws Exception {
        StopWatch stopWatch = new StopWatch();
        FileInputStream fileInputStream = new FileInputStream(file);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        int i;
        while ((i = fileInputStream.read()) != -1) {
            byteArrayOutputStream.write(i);
        }
        fileInputStream.close();

        byte[] data = byteArrayOutputStream.toByteArray();
        logger.debug("send request, url={}, method={}", this.domain + uri, "Post");
        logger.debug("====== http request begin ======");
        HttpPost request = new HttpPost(this.domain + uri);
        prepareHeadersForFile(request);
        logger.debug("JSESSIONID=" + getSessionId());
        request.setHeader("Cookie","JSESSIONID=" + getSessionId());

        ByteArrayBody byteArrayBody = new ByteArrayBody(data, "portrait.jpg");
        MultipartEntity reqEntity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE);
        reqEntity.addPart("file", byteArrayBody);

        request.setEntity(reqEntity);

        HttpResponse response =createDefaultHttpClient().execute(request);

        setSessionId(response);

        logger.debug("====== http request end ======");
        logger.debug("received response, statusCode={}, elapsed={}", getStatusCode(response), stopWatch.elapsedTime());

        int responseCode = getStatusCode(response);
        String responseText = getResponseText(response);
        request.releaseConnection();
        validateStatusCode(responseCode, responseText);
        return JSONBinder.binder(responseClass).fromJSON(responseText);
    }
}
